#include <prism/backend/generation/llvm/llvm-main.hpp>
//=====================================================================
#include <boost/lexical_cast.hpp>
//=====================================================================
#include <atma/string_builder.hpp>
//=====================================================================
#include <sooty/frontend/syntactic_analysis/algorithm.hpp>
//=====================================================================
#include <prism/frontend/syntactical_analysis/marshall.hpp>
#include <prism/frontend/syntactical_analysis/parse.hpp>
#include <prism/frontend/semantic_analysis/decorate.hpp>
#include <prism/frontend/semantic_analysis/semantic_analysis.hpp>
#include <prism/frontend/semantic_analysis/resolve/identifier.hpp>
#include <prism/frontend/semantic_analysis/resolve/function.hpp>
#include <prism/frontend/semantic_analysis/resolve/type.hpp>
#include <prism/backend/generation.hpp>

//=====================================================================
//=====================================================================
using namespace prism;


//=====================================================================
//
// this is big function!
// it's for "mutating expressions"
//=====================================================================
namespace {
	void dereference_expression(sooty::const_parseme_ptr_ref N)
	{
		switch (N->id)
		{
			case ID::identifier:
			{
				sooty::const_parseme_ptr_ref VD = marshall::identifier::variable_definition(N);
				ATMA_ASSERT(!marshall::variable_definition::is_parameter(VD));
				
				sooty::parseme_ptr VD_name = marshall::variable_definition::name(VD);
				sooty::parseme_ptr VD_type = marshall::variable_definition::type(VD);

				llvm::output_stream() << "%" << N->value.string << ".D"
					<< " = load " << llvm::storage_type_name(N) << " " << llvm::lvalue_name(N) << "\n";
			}
		}
	}
}

void llvm::pre_mutate_expression(sooty::const_parseme_ptr_ref N)
{
	switch (N->id)
	{
	
		case ID::identifier:
		{
		/*
			sooty::const_parseme_ptr_ref VD = marshall::identifier::variable_definition(N);

			if ( !marshall::variable_definition::is_parameter(VD) )
			{
				sooty::parseme_ptr VD_name = marshall::variable_definition::name(VD);
				sooty::parseme_ptr VD_type = marshall::variable_definition::type(VD);

				N->value.integer = VD_name->value.integer++;

				output_stream() << "%" << N->value.string //<< "." << N->value.integer
					<< " = load " << logical_type_name(VD_type) << "* %" << N->value.string << "\n";
			}
		*/
			break;
			
		}
		
		
		case ID::function_call:
		{
			ATMA_HALT("can't do that just yet");
			break;
		}
	
		
		case ID::dereference:
		{
			sooty::parseme_ptr_ref child = N->children[0];
			dereference_expression(child);
			break;
		}
		
		
		case ID::add:
		case ID::sub:
		case ID::mul:
		case ID::div:
		{
			ATMA_HALT("can't do that just yet");
			break;
		}
		
		
		default:
			ATMA_HALT("terrible~~~!");
	}
	
	
}



//=====================================================================
//
// this is big function!
// it's for "accessing expressions"
//
//=====================================================================
void llvm::access_expression(sooty::const_parseme_ptr_ref N)
{
	switch (N->id)
	{
		case ID::function_call:
		{
			sooty::parseme_ptr function_matching_N = sooty::upwards_bredth_first_find_first_if(N, prism::resolve::function_matches_function_call_pred(N));
			ATMA_ASSERT(function_matching_N);
			if (!function_matching_N) return;
			
			// before we print our function definition, we need to evaluate all the expressions that
			// make up its arguments
			sooty::const_parseme_ptr_ref AL = marshall::function_call::argument_list(N);
			std::for_each(AL->children.begin(), AL->children.end(), access_expression);
			
			output_stream() << rvalue_name(N) << " = call " << logical_type_name(function_matching_N) << " @" << decorate_function(function_matching_N);
			argument_list(AL);
			break;
		}
		
		case ID::dereference:
		{
			sooty::parseme_ptr child = N->children[0];
			access_expression(child);
			output_stream() << rvalue_name(N) << " = load " << storage_type_name(N) << " " << rvalue_name(child) << std::endl;
			break;
		}
		
		
		case ID::assignment:
		{
			pre_mutate_expression( N->children[0] );
			access_expression( N->children[1] );
			
			output_stream()
				<< "store " << logical_type_name(N->children[1]) << " " << rvalue_name(N->children[1])
				<< ", " << storage_type_name(N->children[0]) << " " << lvalue_name(N->children[0]) << "\n";
			
			post_mutate_expression(N->children[0]);
			
			break;
		}
		
		case ID::new_:
		{
			if (N->children.size() > 1) {
				access_expression(N->children[1]);
			}
			output_stream() << rvalue_name(N) << " = malloc " << logical_type_name( resolve::type_of_pred(N->children[0])() ) << std::endl;
			
			if (N->children.size() > 1) {
				output_stream()
					<< "store " << logical_type_name(N->children[1]) << " " << rvalue_name(N->children[1])
					<< ", " << storage_type_name(N->children[0]) << " " << rvalue_name(N) << "\n";
			}
			
			break;
		}
		
		case ID::add:
		case ID::sub:
		case ID::mul:
		case ID::div:
		case ID::equ:
		{
			sooty::const_parseme_ptr_ref lhs = N->children[0];
			sooty::const_parseme_ptr_ref rhs = N->children[1];
			
			access_expression(lhs);
			access_expression(rhs);
			
			std::string stroper;
			switch (N->id)
			{
				case ID::add: stroper = "add"; break;
				case ID::sub: stroper = "sub"; break;
				case ID::mul: stroper = "mul"; break;
				case ID::div: stroper = "sdiv"; break;
				case ID::equ: stroper = "icmp eq"; break;
			}
			
			output_stream() 
				<< rvalue_name(N) << " = " << stroper << " " << logical_type_name(lhs) << " " << rvalue_name(lhs) << ", " << rvalue_name(rhs) << "\n";	
			
			break;
		}
	}
}


void prism::llvm::post_mutate_expression( sooty::const_parseme_ptr_ref N )
{
	switch (N->id)
	{
		case ID::dereference:
			post_mutate_expression(N->children[0]);
			break;
		
		case ID::identifier:
		{
			output_stream()
				<< rvalue_name(N) << " = load " << storage_type_name(N) << " "
				<< "%" << marshall::variable_definition::name( marshall::identifier::variable_definition(N) )->value.string
				<< "\n";
		}
	}
}



